﻿using System;
using System.ComponentModel;
using System.Reflection;
using System.Windows;
using System.Windows.Input;
using System.Windows.Markup;

namespace CardPlay.Client.Controls
{
    /// <summary>
    /// This markup extension locates the first focusable child and returns it.
    /// </summary>
    public class FirstFocusedElementExtension : MarkupExtension
    {
        /// <summary>
        /// Constructor
        /// </summary>
        public FirstFocusedElementExtension()
        {
            OneTime = true;
        }

        /// <summary>
        /// Unhook the handler after it has set focus to the element the first time
        /// </summary>
        public bool OneTime { get; set; }

        /// <summary>
        /// This method locates the first focusable + visible element we can
        /// change focus to.
        /// </summary>
        /// <param name="serviceProvider">IServiceProvider from XAML</param>
        /// <returns>Focusable Element or null</returns>
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            // Ignore if in design mode
            if ((bool)(DesignerProperties
                          .IsInDesignModeProperty
                          .GetMetadata(typeof(DependencyObject)).DefaultValue))
                return null;

            // Get the IProvideValue interface which gives us access to the target property 
            // and object.  Note that MSDN calls this interface "internal" but it's necessary
            // here because we need to know what property we are assigning to.
            var pvt = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
            if (pvt != null)
            {
                var targetProperty = pvt.TargetProperty;
                var frameworkElement = pvt.TargetObject as FrameworkElement;

                if (frameworkElement != null)
                {
                    // If the element isn't loaded yet, then wait for it.
                    if (!frameworkElement.IsLoaded)
                    {
                        RoutedEventHandler deferredFocusHookup = null;
                        deferredFocusHookup = (sender, args) =>
                        {
                            // Ignore if the element is now loaded but not visible -- this happens for things like TabItem.
                            // Instead, we'll wait until the item becomes visible and then set focus.
                            if (frameworkElement.IsVisible == false)
                                return;

                            // Look for the first focusable leaf child and set the property
                            var inputElement = frameworkElement.GetLeafFocusableChild();
                            if (targetProperty is DependencyProperty)
                            {
                                // Specific case where we are setting focused element.
                                // We really need to set this property onto the focus scope, 
                                // so we'll use UIElement.Focus() which will do exactly that.
                                if (targetProperty == FocusManager.FocusedElementProperty)
                                {
                                    inputElement.Focus();
                                }
                                // Being assigned to some other property - just assign it.
                                else
                                {
                                    frameworkElement.SetValue((DependencyProperty) targetProperty, inputElement);
                                }
                            }
                            // Simple property assignment through reflection.
                            else if (targetProperty is PropertyInfo)
                            {
                                var pi = (PropertyInfo) targetProperty;
                                pi.SetValue(frameworkElement, inputElement, null);
                            }

                            // Unhook the handler if we are supposed to.
                            if (OneTime)
// ReSharper disable AccessToModifiedClosure
                                frameworkElement.Loaded -= deferredFocusHookup;
// ReSharper restore AccessToModifiedClosure
                        };

                        // Wait for the element to load
                        frameworkElement.Loaded += deferredFocusHookup;
                    }
                    else
                    {
                        return frameworkElement.GetLeafFocusableChild();
                    }
                }
            }

            return null;
        }
    }
}
